package wangwenjun.phase2.concurrencypattern.eventbus.framework;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * @author ChangLiang
 * @date 2020/6/18
 */
class SubscriberRegistry {

    private final ConcurrentHashMap<String, ConcurrentLinkedQueue<Subscriber>> topicSubscribersMap = new ConcurrentHashMap<>();

    public void bind(Object subscriber) {
        List<Method> subscribeMethods = getSubscribeMethods(subscriber);
        subscribeMethods.forEach(m->convert2Subscriber(subscriber,m));
    }

    public void unbind(Object subscriber) {
        // unbind 为了提高速度， 只对Subscriber 进行失效操作
        topicSubscribersMap.forEach((key,queue)->
            queue.forEach(s->{
                if (s.getSubscribeObject() == subscriber) {
                    s.setDisable(true);
                }
            })
        );
    }

    public ConcurrentLinkedQueue<Subscriber> getSubscribersByTopic(String topic) {
        return topicSubscribersMap.get(topic);
    }

    private void convert2Subscriber(Object subscriber, Method method) {
        EventSubscribe eventSubscribe = method.getDeclaredAnnotation(EventSubscribe.class);
        String topic = eventSubscribe.topic();
        // 当某topic 没有SubscriberQueue 的时候创建一个
        topicSubscribersMap.computeIfAbsent(topic, key -> new ConcurrentLinkedQueue<>());
        // 创建一个Subscriber 并且加入Subscriber 列表中
        topicSubscribersMap.get(topic).add(new Subscriber(subscriber, method));
    }

    private List<Method> getSubscribeMethods(Object subscriber) {
        final List<Method> methods = new ArrayList<>();
        Class<?> temp = subscriber.getClass();
        // 不断获取当前类和父类的所有@EventSubscribe 方法
        while (temp != null) {
            // 获取所有的方法
            Method[] declaredMethods = temp.getDeclaredMethods();
            // 只有public 方法 && 有一个入参 && 最重要的是被@EventSubscribe 标记的方法才符合回调方法
            Arrays.stream(declaredMethods).filter(
                    m->m.isAnnotationPresent(EventSubscribe.class)
                    && m.getParameterCount() == 1
                    && m.getModifiers() == Modifier.PUBLIC
            ).forEach(methods::add);
            temp = temp.getSuperclass();
        }
        return methods;
    }
}
